#include "../include/lz77.h"
#include <stdio.h>


cLZ77::cLZ77(int window_size,int buffer_size)
    :cur_pos(-1),
    winbuf(window_size,buffer_size)
#if WINBUF_LIST    
    ,window(window_size),
    buffer(window_size),
    witr(&this->window),
    bitr(&this->buffer)
#endif //WINBUF_LIST        
{
  
}

cLZ77::~cLZ77() {}

void cLZ77::Clear()
{
  this->cur_pos=-1;
  this->winbuf.clear();
  this->winbuf.clear_buffer();
#if WINBUF_LIST  
  this->window.clear();
  this->buffer.clear();
#endif //WINBUF_LIST  

}


bool cLZ77::Flush(lz77_code * code)
{
#if WINBUF_LIST
  this->FlushL(code);
#else
  if(this->winbuf.get_buffer_length()==0)
    return false;
  code->backward_distance = get_back_dist(this->cur_pos);
  code->lenght = this->winbuf.get_buffer_length()-1;
  code->literal= this->winbuf(this->winbuf.get_buffer_length()-1);
  this->winbuf.flush_buffer();
  this->cur_pos=0;
  return true;
#endif //WINBUF_LIST
}


bool cLZ77::Encode(uint8_t val,lz77_code * code)
{
#if WINBUF_LIST
  return this->EncodeL(val,code);
#else  
  if(this->winbuf.get_window_length() == 0)
  {
    //initial situation
    this->winbuf.insert(val);
    code->backward_distance=0;
    code->lenght = 0;
    code->literal = val;
    this->cur_pos=0;
    return true;
  }
  if(this->winbuf.get_buffer_length() == this->winbuf.get_buffer_size())
  {
    code->backward_distance = get_back_dist(this->cur_pos);
    code->lenght = this->winbuf.get_buffer_length();
    code->literal=val;   
    this->winbuf.flush_buffer();
    this->winbuf.insert(val);
    this->cur_pos=0;
    return true;
  }
  if(this->winbuf.get_buffer_length()==0)
  {
    for(int i = 0 ; i < this->winbuf.get_window_length();i++)
    {
      if(this->winbuf[i] == val)
      {
	//first literal found in window
	this->cur_pos = i;
	this->winbuf.buffer(val);
	return false;
      }
    }
    //not found any literal in window
    this->winbuf.insert(val);
    code->backward_distance=0;
    code->lenght=0;
    code->literal = val;
    return true;
  }
  //some data in buffer
  if(this->cur_pos+1 >= this->winbuf.get_window_length())
  {
    //we are at the end of window but buffer is not empty
    //so we insert data from buffer to window and return code
    code->backward_distance = get_back_dist(this->cur_pos);
    code->lenght = this->winbuf.get_buffer_length();
    code->literal=val;
    this->winbuf.flush_buffer();
    this->winbuf.insert(val);
    this->cur_pos=0;
    return true;
  }
  if(this->winbuf[this->cur_pos+1] == val)
  {
    this->cur_pos++;
    this->winbuf.buffer(val);
    return false;
  }
  int temp = this->cur_pos;
  for(int i = this->cur_pos+1 ; i<this->winbuf.get_window_length() ; i++)
  {
    int counter = 0;
    for(int j = 0 ; j < this->winbuf.get_buffer_length() ;j++)
    {
      if(this->winbuf[i+j] == this->winbuf(j))
      {
	counter++;
      }
      else
	break;
    }
    if(counter == this->winbuf.get_buffer_length() && this->winbuf.get_window_length() > i+counter && this->winbuf[i+counter] == val)
    {
      temp = i;
      break;
    }
  }
  if(temp != this->cur_pos)
  {
    this->cur_pos = temp + this->winbuf.get_buffer_length();
    this->winbuf.buffer(val);
    return false;
  }
  code->backward_distance = get_back_dist(this->cur_pos);
  code->lenght = this->winbuf.get_buffer_length();
  code->literal=val;
  this->winbuf.flush_buffer();
  this->winbuf.insert(val);
  this->cur_pos=0;
  return true;
#endif //WINBUFF_LIST  
}

bool cLZ77::Decode(lz77_code * code,cWindowBuffer * buff)
{
  if(code->backward_distance <0 || code->lenght <0 )
  {
      return false;
  }
  if(code->backward_distance == 0)
  {
    if(code->lenght!=0)
    {
      return false;
    }
    buff->insert(code->literal);
    this->winbuf.insert(code->literal);
    return true;
  }
  int i = this->winbuf.get_window_length()-code->backward_distance;
  if(i<0 || i >= this->winbuf.get_window_length())
  {
    exit(1);
    return false;    
  }
  if(i + code->lenght > this->winbuf.get_window_length())
  {
    exit(1);
    return false;    
  }
  for(int j = 0 ; j < code->lenght ; j++)
  {
    this->winbuf.buffer(this->winbuf[i+j]);
    buff->insert(this->winbuf[i+j]);
  }
  buff->insert(code->literal);
  this->winbuf.flush_buffer();
  this->winbuf.insert(code->literal);
  return true;
}



int cLZ77::get_back_dist(int index)
{
  return (this->winbuf.get_window_length() - (index-this->winbuf.get_buffer_length()+1));
}

#if WINBUF_LIST
int cLZ77::get_back_distL(int index)
{
  return (this->window.getSize() - (index-this->buffer.getSize()+1));
}

void cLZ77::flush_buffer(lz77_code * code,uint8_t val)
{
  code->backward_distance = get_back_distL(this->witr.index());
  code->lenght = this->buffer.getSize();
  code->literal=val;
  this->window.flush(this->buffer);
  this->window.insert(val);
  this->witr.first(); 
}

bool cLZ77::FlushL(lz77_code * code)
{
  if(this->buffer.empty())
    return false;
  code->backward_distance = get_back_distL(this->witr.index());
  code->lenght = this->buffer.getSize()-1;
  Iterator<uint8_t> bitr(&this->buffer);
  bitr.last();
  code->literal=bitr();
  this->window.flush(this->buffer);;
  this->witr.first();
  return true;
}

bool cLZ77::EncodeL(uint8_t val,lz77_code * code)
{
    if(this->window.empty())
    {
      /* Window empty, insert to window and return */
      this->window.insert(val);
      code->backward_distance=0;
      code->lenght = 0;
      code->literal = val;
      this->witr.first();
      return true;
    }
    if(this->buffer.full())
    {
      /* Buffer is full, flush buffer to window, insert val to window */
      this->flush_buffer(code,val);
      return true;
    }
    if(this->buffer.empty())
    {
      /* Empty buffer */
      for(Iterator<uint8_t> itr(&this->window) ; !itr ; ++itr)
      {
	if(itr() == val)
	{
	  //first literal found in window
	  this->witr = itr;
	  this->buffer.insert(val);
	  return false;
	}
      }
      //not found any literal in window
      this->window.insert(val);
      code->backward_distance=0;
      code->lenght=0;
      code->literal = val;
      return true;
    }
    //some data in buffer
    if(this->witr.is_last())
    {
	//we are at the end of window but buffer is not empty
	//so we insert data from buffer to window and return code
	this->flush_buffer(code,val);
	return true;
    }
    Iterator<uint8_t> itr(this->witr);
    ++itr;
    if(itr() == val)
    {
      //next literal in window
      ++this->witr;      
      this->buffer.insert(val);
      return false;
    }
//     int temp = this->cur_pos;    
//     for(int i = this->cur_pos+1 ; i<this->winbuf.get_window_length() ; i++)
    Iterator<uint8_t> temp_itr(this->witr);
    Iterator<uint8_t> temp_witr(itr);
    for( ; !itr ; ++itr)
    {
      int counter = 0;
      temp_witr = itr;
      for( Iterator<uint8_t> bitr(&this->buffer) ; !bitr && !temp_witr; ++bitr,++temp_witr )
      {
	if(temp_witr() == bitr())
	{
	  counter++;
	}
	else
	  break;
      }
      if(counter == this->buffer.getSize() && this->window.getSize() > itr.index()+counter && temp_witr() == val)
      {
	temp_itr = itr;
	break;
      }
    }
    if(temp_itr != this->witr)
    {
      this->witr = temp_witr;
      this->buffer.insert(val);
      return false;
    }
    this->flush_buffer(code,val);
    return true;

}
#endif //WINBUF_LIST